﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// Simple Service Administration Tool for WinNT based systems.
// Copyright © 2010 - Silvan Gehrig
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
// Author:
//  Silvan Gehrig
//
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Principal;
using System.ServiceModel;

using SSATool.Common.Util;
using SSATool.Common.Util.ExtensionMethods;
using SSATool.Common.Util.Threading;
using SSATool.DL.Client.AdminService;
using SSATool.DL.Client.EventService;
using SSATool.DL.Common;
using SSATool.DL.Common.Dto;

#endregion

namespace SSATool.DL.Client
{
    /// <summary>
    /// Represents the admin WCF server instance.
    /// </summary>
    public class ServiceWcfClient : DisposeBase, ServiceEventServerCallback, IClientDataProvider
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const string UrlService = "net.tcp://{0}:8080/{1}";
        private const string AdminServiceName = "ServiceAdminService";
        private const string EventServiceName = "ServiceEventService";

        private readonly IDataSyncDispatcher _dataDispatcher;
        private ServiceEventServerClient _eventClient;
        private ServiceAdminServerClient _adminClient;
        
        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Represents the an event which is fired if the data on the
        /// server has changed.
        /// </summary>
        public event EventHandler<ClientDataEventArgs> Update;

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceWcfClient"/> class.
        /// </summary>
        public ServiceWcfClient(IDataSyncDispatcher dataDispatcher)
        {
            PreCondition.AssertNotNull(dataDispatcher, "dataDispatcher");

            _dataDispatcher = dataDispatcher;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Receives the changes on a service from the host.
        /// </summary>
        /// <param name="services">Specifies the service to update.</param>
        void ServiceEventServerCallback.SendChange(ServiceControllerChange[] services)
        {
            _dataDispatcher.Invoke(() => OnUpdate(new ClientDataEventArgs(services)));
        }

        /// <summary>
        /// Starts the WCF client in an asynchronous manner.
        /// </summary>
        /// <param name="destination">Specifies the destination host address.</param>
        /// <param name="password">Specifies the user name of the destination host, which is used to log in.</param>
        /// <param name="username">Specifies the password of the destination host, which is used to log in.</param>
        /// <param name="callback"></param>
        public void StartAsync(string destination, string username, string password, Action<Exception> callback)
        {
            AsyncProcess asyncProc = new AsyncProcess();
            asyncProc.Finished += (sender, e) =>
                                      {
                                          _dataDispatcher.Invoke(() => callback(e.Exception));
                                      };
            asyncProc.Run(() => Start(destination, username, password));
        }

        /// <summary>
        /// Starts the WCF client.
        /// </summary>
        /// <param name="destination">Specifies the destination host address.</param>
        /// <param name="password">Specifies the user name of the destination host, which is used to log in.</param>
        /// <param name="username">Specifies the password of the destination host, which is used to log in.</param>
        public void Start(string destination, string username, string password)
        {
            PreCondition.AssertNotNull(destination, "destination");

            try
            {
                // Instruct the ServiceHost that the type
                // that is used is a ServiceLibrary.service1
                NetTcpBinding tcpBinding = new NetTcpBinding();
                tcpBinding.TransactionFlow = false;
                tcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
                tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
                tcpBinding.Security.Mode = SecurityMode.Transport;

                EndpointAddress addressAdminSvc = new EndpointAddress(string.Format(UrlService, destination, AdminServiceName));
                EndpointAddress addressEventSvc = new EndpointAddress(string.Format(UrlService, destination, EventServiceName));

                NetworkCredential credentials = (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
                    ? CredentialCache.DefaultNetworkCredentials
                    : new NetworkCredential(username, password);

                InstanceContext context = new InstanceContext(this);
                _eventClient = new ServiceEventServerClient(context, tcpBinding, addressEventSvc);
                _eventClient.ClientCredentials.Windows.ClientCredential = credentials;
                _eventClient.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;
                _eventClient.Open();
                _eventClient.Subscribe();

                _adminClient = new ServiceAdminServerClient(tcpBinding, addressAdminSvc);
                _adminClient.ClientCredentials.Windows.ClientCredential = credentials;
                _adminClient.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;
                _adminClient.Open();
            }
            catch (Exception)
            {
                Stop();
                throw;
            }
        }

        /// <summary>
        /// Stops the WCF server.
        /// </summary>
        public void Stop()
        {
            if (_eventClient != null)
            {
                if (_eventClient.State != CommunicationState.Faulted)
                {
                    _eventClient.Unsubscribe();
                    _eventClient.Close();
                }
                _eventClient.TryDispose<CommunicationObjectFaultedException>();
                _eventClient = null;
            }

            if (_adminClient != null)
            {
                if (_adminClient.State != CommunicationState.Faulted)
                {
                    _adminClient.Close();
                }
                _adminClient.TryDispose<CommunicationObjectFaultedException>();
                _adminClient = null;
            }
        }



        /// <summary>
        /// Gets a list of all service controllers.
        /// </summary>
        /// <returns>Returns the service list.</returns>
        public IList<ServiceController> GetServiceList()
        {
            return _adminClient.GetServiceList();
        }

        /// <summary>
        /// Gets the service with the given unique name.
        /// </summary>
        /// <param name="uniqueServiceName">Name of the service to retrieve.</param>
        /// <returns>Returns the retrieved service.</returns>
        public ServiceController GetService(string uniqueServiceName)
        {
            return _adminClient.GetService(uniqueServiceName);
        }

        /// <summary>
        /// Sends the changes on a service back to the host.
        /// </summary>
        /// <param name="service">Specifies the service to update.</param>
        public void SetService(ServiceController service)
        {
            _adminClient.SetService(service);
        }

        /// <summary>
        /// Releases managed (c# or .net) resources.
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            Stop();
        }

        /// <summary>
        /// Fires the DataUpdate event.
        /// </summary>
        /// <param name="eventArgs">Specifies the event args with the updated elements.</param>
        protected virtual void OnUpdate(ClientDataEventArgs eventArgs)
        {
            if (Update != null)
            {
                Update(this, eventArgs);
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
